package com.blt.govdata.utils;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.nio.charset.Charset;
import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

import static com.chinawiserv.apps.logger.Logger.errorThrow;
import static com.chinawiserv.apps.logger.Logger.info;

@Component
public class DESPlusUtil {

    private final LinkedBlockingQueue<CipherAutoRelase> DECRYPT_MODE_POOL = new LinkedBlockingQueue<>();
    private final LinkedBlockingQueue<CipherAutoRelase> ENCRYPT_MODE_POOL = new LinkedBlockingQueue<>();
    @Value("${desplus.pool.count : 0}")
    private int desplus_pool_count;
    private AlgorithmParameterSpec iv = null;

    private Key key = null;

    @Value("${desplus.privatekey :_service}")
    private String privateKey;

    private Charset defualtCharset = Charset.forName("utf-8");

    public static void main(String[] args) throws Exception {

        final DESPlusUtil util = new DESPlusUtil();
        util.privateKey = "_service";
        util.desplus_pool_count = 10;
        util.init();

        final String encodeStr = util.encode("ptjxj=bc7f61880606941da1cd02436208ca9e", Charset.defaultCharset());
        System.out.println(encodeStr);

        final String decodeStr = util.decode("dfee1bee6b9c88de954175e46edfe150aaf1f3a61db33f5af64220277f25c6ff0458b07d23b10d99", Charset.defaultCharset());
        System.out.println(decodeStr);

    }

    public String decode(String data) {
        return this.decode(data, this.defualtCharset);
    }

    public String decode(String data, Charset charset) {

        try (CipherAutoRelase deCipher = this.cipher(Cipher.DECRYPT_MODE)) {

            final byte[] pasByte = deCipher.doFinal(this.hexStr2ByteArr(data));
            return new String(pasByte, charset);

        } catch (final Exception e) {
            throw errorThrow("解密字符串 {} 异常 ", data);
        }

    }

    public String encode(String data) {
        return this.encode(data, this.defualtCharset);
    }

    public String encode(String data, Charset charset) {

        try (CipherAutoRelase enCipher = this.cipher(Cipher.ENCRYPT_MODE);) {

            final byte[] pasByte = enCipher.doFinal(data.getBytes(charset));
            return this.byteArr2HexStr(pasByte);

        } catch (final Exception e) {
            throw errorThrow("数据加密异常, 异常信息 :{}", e.getMessage());
        }

    }

    private String byteArr2HexStr(byte[] arrB) {

        final int iLen = arrB.length;
        // 每个byte用两个字符才能表示，所以字符串的长度是数组长度的两倍
        final StringBuffer sb = new StringBuffer(iLen * 2);

        for (int i = 0; i < iLen; i++) {

            int intTmp = arrB[i];
            // 把负数转换为正数
            while (intTmp < 0) {
                intTmp = intTmp + 256;
            }

            // 小于0F的数需要在前面补0
            if (intTmp < 16) {
                sb.append("0");
            }
            sb.append(Integer.toString(intTmp, 16));
        }
        return sb.toString();
    }

    private CipherAutoRelase cipher(int opmode) {

        try {

            if (opmode == Cipher.DECRYPT_MODE)
                return this.DECRYPT_MODE_POOL.poll(10, TimeUnit.SECONDS);
            if (opmode == Cipher.ENCRYPT_MODE)
                return this.ENCRYPT_MODE_POOL.poll(10, TimeUnit.SECONDS);

        } catch (final InterruptedException e) {
            return new CipherAutoRelase(opmode, false);
        }
        throw new IllegalArgumentException("mode[" + opmode + "] does not match any of Cipher.DECRYPT_MODE[" + Cipher.DECRYPT_MODE
                + "] and Cipher.ENCRYPT_MODE[" + Cipher.ENCRYPT_MODE + "]");
    }

    private Cipher createCipherByMode(int opmode) {

        try {
            final Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
            cipher.init(opmode, this.key, this.iv);
            return cipher;
        } catch (final Exception e) {
            e.printStackTrace();
            throw errorThrow("创建Cipher对象失败, 类型 {}", opmode == Cipher.DECRYPT_MODE ? "Cipher.DECRYPT_MODE" : "Cipher.ENCRYPT_MODE", e);
        }
    }

    /**
     * 将表示16进制值的字符串转换为byte数组， 和public String byteArr2HexStr(byte[] arrB) 互为可逆的转换过程
     * @param strIn 需要转换的字符串
     * @return 转换后的byte数组
     * @throws Exception 本方法不处理任何异常，所有异常全部抛出
     * @author <a href="mailto:leo841001@163.com">LiGuoQing</a>
     */
    private byte[] hexStr2ByteArr(String strIn) {

        final byte[] arrB = strIn.getBytes();
        final int iLen = arrB.length;

        // 两个字符表示一个字节，所以字节数组长度是字符串长度除以2
        final byte[] arrOut = new byte[iLen / 2];
        for (int i = 0; i < iLen; i = i + 2) {
            final String strTmp = new String(arrB, i, 2);
            arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16);
        }
        return arrOut;
    }

    @PostConstruct
    private void init() throws Exception {

        final byte[] dESkey = this.privateKey.getBytes();
        final byte[] dESIV = dESkey.clone();

        this.iv = new IvParameterSpec(dESIV);// 设置向量
        this.key = SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec(dESkey));// 得到密钥

        info("加密解密池初始化数量 : {}", this.desplus_pool_count);

        IntStream.range(0, this.desplus_pool_count).parallel().forEach(i -> {
            try (final CipherAutoRelase decrypt_mode = new CipherAutoRelase(Cipher.DECRYPT_MODE, true);
                 final CipherAutoRelase encrypt_mode = new CipherAutoRelase(Cipher.ENCRYPT_MODE, true)) {
            } catch (Exception e) {
                throw errorThrow("加密工具初始化错误", e);
            }
        });

    }

    private class CipherAutoRelase implements AutoCloseable {

        final Cipher cipher;

        final int opmode;

        final boolean pooled;

        public CipherAutoRelase(int opmode, boolean pooled) {
            super();
            this.opmode = opmode;
            this.cipher = DESPlusUtil.this.createCipherByMode(opmode);
            this.pooled = pooled;
        }

        @Override
        public void close() throws Exception {

            if (!this.pooled)
                return;

            if (this.opmode == Cipher.DECRYPT_MODE) {
                DESPlusUtil.this.DECRYPT_MODE_POOL.offer(this);
            } else if (this.opmode == Cipher.ENCRYPT_MODE) {
                DESPlusUtil.this.ENCRYPT_MODE_POOL.offer(this);
            }
        }

        public byte[] doFinal(byte[] input) throws IllegalBlockSizeException, BadPaddingException {
            return this.cipher.doFinal(input);
        }
    }
}
